﻿using DotNetExtensions.Core.Encryptions;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace DotNetExtensions.Core.FileTransfers
{
    public static class MutipleFormDataExtensions
    {
        public static MutipleFormValue Try(this IFormCollection formCollection, string tempPath = default)
        {
            MutipleFormValue response = new MutipleFormValue();
            if (formCollection == null) throw new Exception("Mutiple form data invalid.");
            if (!formCollection.Files.Any()) throw new Exception("Mutiple form data is empty.");
            IFormFile file = formCollection.Files.First();
            string fileChunkOption = formCollection["FileChunkOption"];
            string filename;

            if (string.IsNullOrEmpty(fileChunkOption))
            {
                response.Content = UploadFile(file, out filename, tempPath);
            }
            else
            {
                var option = JsonConvert.DeserializeObject<FileChunkOption>(fileChunkOption);

                response.Index = option.Index;
                response.Total = option.Size;

                response.Content = UploadChunkFile(file, option, out filename, tempPath);
            }
            response.FileName = filename;
            return response;
        }
        public static MutipleFormValue<T> Try<T>(this IFormCollection formCollection, string tempPath = default)
        {
            MutipleFormValue<T> response = new MutipleFormValue<T>();
            if (formCollection == null) throw new Exception("Mutiple form data invalid.");
            if (!formCollection.Files.Any()) throw new Exception("Mutiple form data is empty.");
            IFormFile file = formCollection.Files.First();
            string fileChunkOption = formCollection["FileChunkOption"];
            string filename;

            string extraData = formCollection["ExtraData"];

            if (!string.IsNullOrEmpty(extraData))
            {
                response.ExtraData = JsonConvert.DeserializeObject<T>(extraData);
            }

            if (string.IsNullOrEmpty(fileChunkOption))
            {
                response.Content = UploadFile(file, out filename, tempPath);
            }
            else
            {
                var option = JsonConvert.DeserializeObject<FileChunkOption>(fileChunkOption);

                response.Index = option.Index;
                response.Total = option.Size;

                response.Content = UploadChunkFile(file, option, out filename, tempPath);
            }
            response.FileName = filename;
            return response;
        }
        private static byte[] UploadFile(IFormFile file)
        {
            try
            {
                byte[] content = null;
                using (var stream = file.OpenReadStream())
                {
                    if (!stream.CanRead) throw new Exception("Stream can't read");
                    content = new byte[stream.Length];
                    stream.Read(content, 0, content.Length);
                }

                return content;
            }
            catch (Exception)
            {
                throw;
            }
        }
        private static byte[] UploadFile(IFormFile file, out string filename, string tempPath = default)
        {
            try
            {
                filename = "";
                string dest = "";
                if (string.IsNullOrEmpty(tempPath)) return UploadFile(file);

                if (!Directory.Exists(tempPath)) throw new Exception($"The specified directory does not exist `{tempPath}`");

                using (var stream = file.OpenReadStream())
                {
                    if (!stream.CanRead) throw new Exception("Stream can't read");
                    filename = string.Format("{0}.temp", Snowflake.Default.NextId());

                    dest = Path.Combine(tempPath, filename);

                    using (FileStream fs = new FileStream(dest, FileMode.Create, FileAccess.Write))
                    {
                        file.CopyTo(fs);
                    }
                }

                return null;
            }
            catch (Exception)
            {
                throw;
            }
        }
        private static byte[] UploadChunkFile(IFormFile file, FileChunkOption option, out string filename, string tempPath = default)
        {
            try
            {
                filename = "";
                byte[] content = null;
                using (var stream = file.OpenReadStream())
                {
                    if (!stream.CanRead) throw new Exception("Stream can't read");
                    string chunkMd5 = MD5.Hash(stream);
                    if (chunkMd5 != option.ChunkMD5) throw new Exception("File buffer invalid");

                    if (string.IsNullOrEmpty(tempPath))
                    {
                        content = new byte[stream.Length];
                        stream.Read(content, 0, content.Length);
                    }
                    else
                    {
                        filename = GetChunkFileName(tempPath, option);
                        string dest = Path.Combine(tempPath, filename);
                        if (File.Exists(dest))
                        {
                            using (FileStream fs = new FileStream(dest, FileMode.Create, FileAccess.Write))
                            {
                                file.CopyTo(fs);
                            }
                        }

                        if (option.IsLastChunk)
                        {
                            filename = MargeFiles(tempPath, option);
                        }
                    }
                }

                return content;
            }
            catch (Exception)
            {
                throw;
            }
        }

        private static string MargeFiles(string tempPath, FileChunkOption option)
        {
            string filePath = Path.Combine(tempPath, option.FileMD5);
            string[] files = Directory.GetFiles(filePath);
            string filename = option.FileMD5 + ".temp";
            string dest = Path.Combine(tempPath, filename);

            if (!File.Exists(dest))
            {
                using (FileStream full = new FileStream(dest, FileMode.Create, FileAccess.Write))
                {
                    foreach (var item in files)
                    {
                        byte[] byts = File.ReadAllBytes(item);
                        full.Write(byts);
                    }
                }

                Directory.Delete(filePath, true);
            }

            return filename;
        }
        private static string GetChunkFileName(string tempPath, FileChunkOption option)
        {
            string chunkName = $"{option.Index}_{option.Size}_{option.ChunkMD5}.temp";
            string filePath = Path.Combine(tempPath, option.FileMD5);
            if (!Directory.Exists(filePath)) Directory.CreateDirectory(filePath);

            return Path.Combine(option.FileMD5, chunkName);
        }
    }
}
